package yu.service;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import yu.domain.PkSeed;
import yu.mapper.PkSeedMapper;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 主键生成服务，用于生成 long、流水号 类型的主键
 */
@Service
public class PrimaryKeyService implements InitializingBean, DisposableBean {

    /** JVM中生成长整型主键的种子*/
    protected AtomicLong atomicLong = null;

    /** 集群环境下应该记录节点号, 最大值15 */
    @Value("${node.id:0}")
    protected int nodeID;

    /**
     * 最大重试次数
     */
    private int maxRetryTimes = 10;

    private static final int INTERVAL = 200;

    @Autowired
    private PkSeedMapper pkSeedMapper;

    @Override
    public void afterPropertiesSet() {
        assert nodeID<=0xF:"怎么会有那么多节点？节点号必须小于16";
        long time = System.currentTimeMillis();
        this.atomicLong = new AtomicLong(time^((long)nodeID<<59));
        GradeCodeGenerator.setPrimaryKeyService(this);
    }


    @Override
    public void destroy() {
        GradeCodeGenerator.setPrimaryKeyService(null);
    }

    /**
     * 获取长整形主键
     * @return
     */
    public long getLongKey() {
        return atomicLong.getAndIncrement();
    }

    /**
     * 根据业务类型，获取长整型主键，效率低
     * @param type
     * @return
     */
    public long getLongKey(String type){
        return getLongKeyInterval(type, 1);
    }

    /**
     * 根据业务类型，获取长整型主键 , 效率更高
     * @param type
     * @return
     */
    public long getLongKeyAndInterval(String type){
        return getLongKeyInterval(type, INTERVAL);
    }

    /**
     * 返回 00001,00002 这样的序列号
     * @param type
     * @param length 序列号的长度
     * @return
     */
    public String getSerialNumber(String type, int length){
        long longKey = getLongKey(type);
        return String.format("%0" + length + "d", longKey);
    }

    /**基于数据库的主键种子映射表*/
    private static ConcurrentMap<String, AtomicLong> keyMap = new ConcurrentHashMap<String, AtomicLong>();
    /**基于数据库的主键上限映射表*/
    private static ConcurrentMap<String, AtomicLong> keyBoundMap = new ConcurrentHashMap<String, AtomicLong>();

    private long getLongKeyInterval(String type, int interval){
        synchronized(type.intern()){//如果对字符串进行加锁，必须使用 intern()
            AtomicLong minVal = getV(keyMap, type);
            AtomicLong maxVal = getV(keyBoundMap, type);
            if (minVal.get() == 0 || minVal.get() > maxVal.get()) {//当前类型的主键已经用完，必须向数据库重新申请
                long maxId = getByInterval(type, interval);
                minVal.set(maxId - interval +1);
                maxVal.set(maxId);
            }
            return minVal.getAndIncrement();
        }
    }

    public List<Long> getLongRange(String type, int count){
        long maxId = getByInterval(type, count);
        ArrayList<Long> list = new ArrayList<>();
        list.add(maxId - count + 1);
        list.add(maxId);
        return list;
    }

    private AtomicLong getV(ConcurrentMap<String, AtomicLong> map, String type){
        AtomicLong dft = new AtomicLong(0);
        AtomicLong v = map.putIfAbsent(type, dft);
        if (v == null) {
            v = dft;
        }
        return v;
    }

    /**
     * 获取流水号
     * @param type 种子类型
     * @param interval 区间（数量）
     * @return
     */
    private long getByInterval(String type, int interval) {
        if(type==null || type.isEmpty()){
            throw new IllegalArgumentException("业务类型(type)，不能为空");
        }
        long value = 0;
        int updateResult = 0;
        int retryTimes = 0;
        //当多个线程请求获取seed时，只有一个会成功，所以使用循环，让失败的线程继续获取
        do{
            if (retryTimes >= maxRetryTimes) {
                throw new IllegalArgumentException("服务器繁忙，请稍后再试");
            }
            PkSeed oldSeed = pkSeedMapper.selectById(type);
            if(oldSeed != null){
                value = oldSeed.getValue();
                updateResult = pkSeedMapper.update(oldSeed.getType(), interval, oldSeed.getVersion());
            }else{
                oldSeed = new PkSeed();
                oldSeed.setType(type);
                oldSeed.setValue(interval);
                oldSeed.setVersion(0);
                updateResult = pkSeedMapper.insert(oldSeed);
            }
            retryTimes++;
        }while (updateResult != 1);
        return value + interval;
    }

}

